/*
 *	监听socket练级的基础抽象模块,带有简单的flood攻击保护
 */
package labox.innovation.loginserver;

import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javolution.util.FastMap;
import labox.innovation.config.Config;

/**
 *
 * @author -Wooden-
 *
 */
public abstract class FloodProtectedListener extends Thread {
	
	private static final Logger _log = LoggerFactory.getLogger(FloodProtectedListener.class.getName());

	private Map<String, ForeignConnection> _floodProtection = new FastMap<String, ForeignConnection>();

	private String _listenIp;

	private int _port;

	private ServerSocket _serverSocket;

	public FloodProtectedListener(String listenIp, int port) throws IOException {
		_listenIp = listenIp;
		_port = port;
		if (_listenIp.equals("*")) {
			_serverSocket = new ServerSocket(_port);//创建接收本地所有IP地址的连接到指定的_port，接收队列最大为50
		} else {
			_serverSocket = new ServerSocket(_port, 50, InetAddress.getByName(_listenIp));
		}
		
	}
	
	protected static class ForeignConnection {
		public int connectionNumber;

		public long lastConnection;

		public boolean isFlooding = false;

		/**
		 * @param time
		 */
		public ForeignConnection(long time) {
			lastConnection = time;
			connectionNumber = 1;
		}
	}

	public abstract void addClient(Socket s);

	public void removeFloodProtection(String ip) {
		if (!Config.FLOOD_PROTECTION_GAMESERVER)
			return;
		ForeignConnection fConnection = _floodProtection.get(ip);
		if (fConnection != null) {
			fConnection.connectionNumber -= 1;
			if (fConnection.connectionNumber == 0) {
				_floodProtection.remove(ip);
			}
		} else {
			_log.warn("Removing a flood protection for a GameServer that was not in the connection map??? :" + ip);
		}
	}

	@Override
	public void run() {
		Socket socket = null;

		while (true) {
			try {
				socket = _serverSocket.accept();//获得客户端的连接socket
				socket.setKeepAlive(true);  //-- 开启保持活动状态的套接字,设置心跳检测。
				
				if (Config.FLOOD_PROTECTION_GAMESERVER) {  //-- flood 攻击保护设置参数判断
					ForeignConnection fConnection = _floodProtection.get(socket.getInetAddress().getHostAddress());
					if (fConnection != null) {//如果此地址已经有ForeignConnection，检查是不是flood attack
						fConnection.connectionNumber += 1;
						if ((fConnection.connectionNumber > Config.FAST_CONNECTION_LIMIT_GAMESERVER && (System.currentTimeMillis() - fConnection.lastConnection) < Config.NORMAL_CONNECTION_TIME) || (System.currentTimeMillis() - fConnection.lastConnection) < Config.FAST_CONNECTION_TIME || fConnection.connectionNumber > Config.MAX_CONNECTION_PER_IP) {
							fConnection.lastConnection = System.currentTimeMillis();
							socket.close();
							fConnection.connectionNumber -= 1;
							if (!fConnection.isFlooding)
								_log.warn("Potential Flood from " + socket.getInetAddress().getHostAddress());
							fConnection.isFlooding = true;
							continue;
						}
						if (fConnection.isFlooding) //if connection was flooding server but now passed the check
						{
							fConnection.isFlooding = false;
							_log.info(socket.getInetAddress().getHostAddress() + " is not considered as flooding anymore.");
						}
						fConnection.lastConnection = System.currentTimeMillis();
					} else {//此地址重来没有连接过则创建ForeignConnection
						fConnection = new ForeignConnection(System.currentTimeMillis());
						_floodProtection.put(socket.getInetAddress().getHostAddress(), fConnection);
					}
				}
				addClient(socket);
			} catch (Exception e) {
				try {
					socket.close();
				} catch (Exception e2) {
				}
				if (this.isInterrupted()) {
					// shutdown?
					try {
						_serverSocket.close();
					} catch (IOException io) {
						_log.error("FloodProtectedListener", Config.SERVER_ID,io);
					}
					break;
				}
			}
		}
	}

	

	public void close() {
		try {
			_serverSocket.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}